/* eslint no-bitwise: 0, import/no-cycle: 0 */
/* eslint no-mixed-operators: 1 */
import { handleApi, request } from '@packages/lib-api';
import { safeDecode } from '@packages/lib-url';
import { uuid } from '@packages/lib-utils';

import {
    Countdown, // 倒计时组件
    diffTime,
    formatCountDownTime, // 把秒变成时间格式
    formatDate, // 时间戳变字符串
    getCountdown, // 获取倒计时组件
    getStamp, // 字符串转时间戳
    isAfter, // 是否某个时间后
    isBefore, // 是否某个时间前
    isIn, // 是否时间内
} from './time';

/**
 * 获取数据类型
 *
 * @exports WPT/Util/getType
 * @param  {Any} obj 要获取的数据类型
 * @returns {String}  数据类型
 */
export function getType(obj) {
    return Object.prototype.toString.call(obj).split(' ')[1].slice(0, -1).toLowerCase();
}

/**
 * 分割url search key value，返回decode后的[key, value]
 *
 * @exports WPT/Util/splitSearchKeyValue
 * @param {String} [str=''] query
 * @returns {Array}
 */
function splitSearchKeyValue(str = '') {
    const [key, ...valueArr] = str.split('=');
    const value = valueArr.length ? valueArr.join('=') : undefined;
    return [key, value].filter((i) => i !== undefined).map(safeDecode);
}

/**
 * 将query转换成json
 *
 * @exports WPT/Util/urlParamToJson
 * @param {String} [urlParams=window.location.search] query
 * @returns {Object}
 */
function urlParamToJson(urlParams) {
    const search = urlParams || window.location.search.substring(1);
    const paramJson = {};
    const searchs = search.split('&');
    for (let i = 0; i < searchs.length; i++) {
        if (searchs[i].split('=')[0]) {
            paramJson[searchs[i].split('=')[0]] = safeDecode(searchs[i].split('=')[1] || '');
        }
    }

    return paramJson;
}

/**
 * 获取链接参数的值
 *
 * @exports WPT/Util/getQueryString
 * @param  {String} name 参数名字
 * @param  {String} [rawUrl=window.location.href] 链接url
 * @return {String} 参数
 */
function getQueryString(name, rawUrl) {
    let url = rawUrl || window.location.href;
    // eslint-disable-next-line prefer-destructuring
    url = url.split('#')[0];

    const reg = new RegExp(`(^|\\?|&)${name}=([^&]*)(\\s|&|$)`, 'i');
    return reg.test(url) ? safeDecode(RegExp.$2.replace(/\+/g, ' ')) : '';
}

function getLastR() {
    const querys = getQueryString('r').split('-');
    return querys[querys.length - 1];
}

/**
 * 从url上获取query对象
 *
 * @exports WPT/Util/query
 * @param {String} [url=location.href] url
 * @returns {Object}
 */
function query(url = window.location.href) {
    let search = '';
    const obj = {};
    if (typeof search !== 'string') {
        return obj;
    }
    if (url.includes('?')) {
        const a = document.createElement('a');
        a.href = url;
        search = a.search.slice(1);
    } else {
        search = '';
    }
    search.split('&').forEach((i) => {
        const [k, v] = splitSearchKeyValue(i);
        if (k) {
            obj[k] = v;
        }
    });
    return obj;
}

/**
 * 为url添加search参数
 *
 * @exports WPT/Util/params
 * @param  {String} [url=''] url
 * @param  {...Object} args 多个对象
 * @returns {String} 添加参数后的url
 */
function params(url = '', ...args) {
    //  url 只能pathName 不能带参数
    if (!/^((https?:)?\/{2}|\/[^/]*)/.test(url)) {
        console.error('addUrlQuery: 格式错误 url应改是以类似https:// 或者 /aa开头的字符串', url);
    }
    // eslint-disable-next-line no-use-before-define
    const { query: queryObj } = urlParse(url);
    const obj = $.extend(queryObj, ...args);
    const search = Object.keys(obj)
        .map((key) => [key, obj[key]].map(encodeURIComponent).join('='))
        .join('&');

    return url.replace(/(\?|#).*/, '') + (search ? `?${search}` : '');
}

function addUrlParam(oriUrl, strParamName, strParamValue) {
    if (WPT.Util.getQueryString(strParamName, oriUrl)) {
        return oriUrl;
    }
    const addParam = [strParamName, strParamValue].join('=');
    const reg = /(\?|#)/;
    const rule = {
        '?': `?${addParam}&`,
        '#': `?${addParam}#`,
        default: `?${addParam}`,
    };

    if (reg.test(oriUrl)) {
        return oriUrl.replace(reg, (m, n) => rule[n]);
    }
    return oriUrl + rule.default;
}

const base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';

function utf16to8(str) {
    let out = '';
    let i;
    let c;
    const len = str.length;
    for (i = 0; i < len; i++) {
        c = str.charCodeAt(i);
        if (c >= 0x0001 && c <= 0x007f) {
            out += str.charAt(i);
        } else if (c > 0x07ff) {
            out += String.fromCharCode(0xe0 | ((c >> 12) & 0x0f));
            out += String.fromCharCode(0x80 | ((c >> 6) & 0x3f));
            out += String.fromCharCode(0x80 | ((c >> 0) & 0x3f));
        } else {
            out += String.fromCharCode(0xc0 | ((c >> 6) & 0x1f));
            out += String.fromCharCode(0x80 | ((c >> 0) & 0x3f));
        }
    }
    return out;
}

function base64encode(url) {
    const str = utf16to8(url);
    let out = '';
    let i = 0;
    let c1;
    let c2;
    let c3;
    const len = str.length;
    while (i < len) {
        c1 = str.charCodeAt(i++) & 0xff; /* eslint-disable-line no-plusplus */
        if (i === len) {
            out += base64EncodeChars.charAt(c1 >> 2);
            out += base64EncodeChars.charAt((c1 & 0x3) << 4);
            out += '==';
            break;
        }
        c2 = str.charCodeAt(i++); /* eslint-disable-line no-plusplus */
        if (i === len) {
            out += base64EncodeChars.charAt(c1 >> 2);
            out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4));
            out += base64EncodeChars.charAt((c2 & 0xf) << 2);
            out += '=';
            break;
        }
        c3 = str.charCodeAt(i++); /* eslint-disable-line no-plusplus */
        out += base64EncodeChars.charAt(c1 >> 2);
        out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xf0) >> 4));
        out += base64EncodeChars.charAt(((c2 & 0xf) << 2) | ((c3 & 0xc0) >> 6));
        out += base64EncodeChars.charAt(c3 & 0x3f);
    }
    return out;
}

function handelImgHeight(url, width) {
    // 1图片地址，  2.图片宽度   width不传 直接返回图片高度
    const vales = url.match(/-W(\d+)H(\d+)/);
    let height = width || 0;
    if (vales && vales[2]) {
        height = (vales[2] / vales[1]) * (width || vales[1]);
        if (height < 0.02) {
            height = 0.02;
        }
    }
    return height;
}

// eslint-disable-next-line camelcase
function number_format(n) {
    /* eslint-disable-line camelcase */
    // 格式化数字
    if (!n || Number.isNaN(n)) return 0;
    const negative = n < 0;
    n = Math.abs(n);
    const point = n.toString().replace(/\d+(\d*)/, '$1');
    n = parseInt(n);
    if (!n) return `${negative ? '-' : ''}${0}${point}`;
    const ns = n.toString();
    const mm = ns.match(/\d{1,3}(?=(\d{3})+$)/g);
    if (mm == null) return `${negative ? '-' : ''}${ns}${point}`;
    mm.push(ns.replace(mm.join(''), ''));
    return `${negative ? '-' : ''}${mm.join(',')}${point}`;
}

/**
 * 手机号码格式化
 *
 * @exports WPT/Util/formatMobile
 * @param {String} mobile
 * @returns {String}
 */
function formatMobile(mobile = '') {
    // mobile类型应该是number/string类型
    const type = getType(mobile);
    if (!['string', 'number'].includes(type)) {
        console.error(mobile, '应该为string类型');
        return '';
    }
    return mobile.toString().replace(/^(\d{3})(\d{4})(\d{4})$/, '$1-$2-$3');
}

/**
 * 千分位格式化数字
 *
 * @exports WPT/Util/formatNumber
 * @param  {Number|String} number 数字
 * @param  {Number} decimals >=0 小数位数; <0 不新增只保留原小数,并且不保留最后一个0
 * @returns {String}
 */
function formatNumber(number, decimals = 2) {
    const num = Number.parseFloat(number);
    const bit = decimals > 2 ? 2 : decimals; // 小数位数最多二位；
    let ret = '';
    if (bit >= 0) {
        ret = num.toFixed(bit);
    } else {
        ret = String(num.toFixed(2) * 1);
    }

    return ret.replace(/\B(?=(?:\d{3})+(?!\d))/g, ',');
}

/**
 * 格式化倒计时
 *
 * @exports WPT/Util/formatCountDown
 * @param  {Number} number 毫秒数
 * @returns {Object} countDown {hour:string, min:string, sec:string, Msec:string}
 */
function formatCountDown(Msecond) {
    let mSecond = Msecond;

    if (Msecond < 0) {
        mSecond = 0;
    }

    const addZero = (num) => (num < 10 ? '0' : '') + num;

    const hour = addZero(parseInt(mSecond / 3600000, 10));

    const min = addZero(parseInt((mSecond % 3600000) / 60000, 10));

    const sec = addZero(parseInt(((mSecond % 3600000) % 60000) / 1000, 10));

    const Msec = addZero(parseInt((((mSecond % 3600000) % 60000) % 1000) / 10, 10));

    return {
        hour,
        min,
        sec,
        Msec,
    };
}

/**
 * 浏览器刷新（安卓微信浏览器需要添加时间戳）
 *
 * @exports WPT/Util/reload
 */
function reload() {
    if (WPT.os.device === 'wechat' && $.os.android) {
        const q = WPT.Util.query();
        q._t = new Date().getTime();
        window.location.replace(WPT.Util.params(window.location.pathname, q));
    } else {
        window.location.reload();
    }
}

/**
 * 复制字符串到剪贴板
 *
 * @exports WPT/Util/copyToClipboard
 * @param {String} [str=''] 需要复制的字符串
 * @param {String} message  复制成功展示的文案
 * @returns {Boolean}
 */
function copyToClipboard(str = '', message) {
    try {
        // 当且仅当 app 版本号大于等于404时才能使用原生剪贴板功能
        if (WPT.os.wpt && WPT.os.wptAppVerison >= 404) {
            console.warn('调用app的复制到剪贴板方法');
            WPT.wptBridge.call('copyToClipboard', { text: str }, (res) => {
                console.warn(res);
            });
            message && WPT.Modal.tips(message);
            return true;
        }
        const supported = document.queryCommandSupported('copy');
        if (!supported) {
            return false;
        }
        const input = document.createElement('textarea');
        input.value = str;
        input.style.cssText = 'position: absolute; top: -10000px; left: -10000px;';
        document.body.appendChild(input);

        input.setAttribute('readonly', ''); // 避免ios弹出键盘
        input.select();
        input.setSelectionRange(0, input.value.length); // 选中文本
        document.execCommand('copy');
        document.body.removeChild(input);
        message && WPT.Modal.tips(message);
        return true;
    } catch (e) {
        return false;
    }
}

function getRandomNum(Min, Max) {
    const Range = Max - Min;
    const Rand = Math.random();
    return Min + Math.round(Rand * Range);
}

function timeFormat(leftTime) {
    if (leftTime <= 0) {
        return {
            hour: 0,
            minute: 0,
            sec: 0,
        };
    }
    let hour = Math.floor(leftTime / 3600);
    hour = hour < 10 ? `0${hour}` : hour;
    let minute = Math.floor((leftTime % 3600) / 60);
    minute = minute < 10 ? `0${minute}` : minute;
    let sec = leftTime % 60;
    sec = sec < 10 ? `0${sec}` : sec;
    return {
        hour,
        minute,
        sec,
    };
}

/**
 * 替换浏览器url的query参数
 *
 * @exports WPT/Util/mergeQuery
 * @param {Object} [queryObj={}] query对象
 * @param {String} [href=location.href] url
 * @returns {String} url
 */
function mergeQuery(queryObj = {}, href = window.location.href) {
    const a = document.createElement('a');
    a.href = href;
    return WPT.Util.urlAddQuery(a.pathname, $.extend(queryObj, WPT.Util.query(href)));
}

function urlParse(href = window.location.href) {
    const a = document.createElement('a');
    a.href = href;
    const queryObj = {};
    a.search
        .slice(1)
        .split('&')
        .forEach((i) => {
            const [key, value] = splitSearchKeyValue(i);
            if (value !== undefined) {
                queryObj[key] = value;
            }
        });
    return {
        query: queryObj,
        href: a.href,
        hostname: a.hostname,
        origin: a.origin,
        protocol: a.protocol,
        port: a.port,
        pathname: a.pathname,
        search: a.search,
        hash: a.hash,
    };
}

/**
 * 比对对象是否相同
 *
 * @exports WPT/Util/diff
 * @param {*} obj1
 * @param {*} obj2
 */
function diff(obj1, obj2) {
    const type = getType(obj1);
    // 类型不一样返回false
    if (type !== getType(obj2)) {
        return false;
    }

    switch (type) {
        case 'object': {
            const keys = Object.keys(obj1);
            if (keys.length === Object.keys(obj2).length) {
                return keys.every((key) => diff(obj2[key], obj1[key]));
            }
            return false;
        }
        case 'array': {
            return obj1.length === obj2.length && obj1.every((item, index) => diff(item, obj2[index]));
        }
        default: {
            return obj1 === obj2;
        }
    }
}

/**
 * 比对url是否相同，只比对pathname,query是否相同
 *
 * @exports WPT/Util/urlDiff
 * @param {*} url1
 * @param {*} url2
 */
function urlDiff(url1, url2) {
    if (url1 === undefined || url2 === undefined) {
        return false;
    }
    const urlInfo1 = urlParse(url1);
    const urlInfo2 = urlParse(url2);
    // r || c只是统计所用，与业务无关
    delete urlInfo1.query.r;
    delete urlInfo1.query.c;
    delete urlInfo2.query.r;
    delete urlInfo2.query.c;

    return diff(urlInfo1.pathname, urlInfo2.pathname) && diff(urlInfo1.query, urlInfo2.query);
}

/**
 * smooth scroll
 *
 * @exports WPT/Util/smoothScroll
 * @param {Number} scrollTop 滚动到的高度
 * @param {Number} during 滚动时间
 */
function smoothScroll(scrollTop = 0, during = 500) {
    let currentTop = $(window).scrollTop();
    const maxTime = during / 10; // 500ms
    const PER = (scrollTop - currentTop) / maxTime;
    let execTime = 1;
    const interval = setInterval(() => {
        currentTop += PER;
        $(window).scrollTop(currentTop);
        execTime += 1;
        if (execTime > maxTime) {
            window.clearInterval(interval);
        }
    }, 10);
}

/**
 * 删除对象的空key
 *
 * @exports WPT/Util/filterKeys
 * @param {Object} 对象
 * @param {String} keys
 * @returns {Object}
 */
function filterKeys(obj, ...keys) {
    keys.forEach((i) => {
        !obj[i] && delete obj[i];
    });

    return obj;
}

// 执行微信任务
export function execWxJob(callback) {
    if (typeof WeixinJSBridge === 'object' && typeof window.WeixinJSBridge.invoke === 'function') {
        callback();
    } else {
        document.addEventListener('WeixinJSBridgeReady', callback);
    }
}

/**
 * 页面滚动到指定位置
 *
 * @exports WPT/Util/animateScroll
 * @param {Number} scrollTop/scrollLeft 如果值为0，会对滚动性能进行优化
 * @param {{ time: Number, target: String, max: Number, direction: String }} 可选参数
 * @returns
 */
export function animateScroll(endPosition = 0, option = {}) {
    const {
        time = 200, // 动画执行时间 毫秒
        target = window, // 执行动画对象，默认window，也可以只指定选择器/dom
        max = 200, // start/end最大间距,避免【start/end】差距过大，导致页面动画卡顿
        direction = 'vertical', // 滚动方向 horizontal/vertical
    } = option;

    if (!target) return Promise.reject('目标元素不存在');

    const scrollPosition = ((el) => {
        const fn = direction === 'vertical' ? 'scrollTop' : 'scrollLeft';
        return (position) => {
            if (position === undefined) {
                return $(el)[fn]();
            }
            $(el)[fn](position);
        };
    })(target);

    let startPosition = scrollPosition();

    // 对起始位置进行校正
    startPosition =
        endPosition >= startPosition
            ? Math.max(endPosition - max, startPosition)
            : Math.min(endPosition + max, startPosition);

    const maxTime = Math.ceil(time / 16);
    const PER = (endPosition - startPosition) / maxTime;
    let execTime = 1;

    return new Promise((res) => {
        const scroll = () => {
            requestAnimationFrame(() => {
                startPosition += PER;
                execTime += 1;

                if (execTime > maxTime) {
                    scrollPosition(endPosition);
                    res && res();
                } else {
                    scrollPosition(startPosition);
                    scroll();
                }
            });
        };
        scroll();
    });
}

/**
 * 平滑水平滚动
 * @param target 需要滚动的容器
 * @param endPosition scrollLeft
 * @param ops {{ time: Number, max: Number, }} 可选参数
 * @return {Promise<unknown>}
 */
export const animateScrollLeft = (target, endPosition, ops) =>
    animateScroll(endPosition, {
        ...ops,
        target,
        direction: 'horizontal',
    });

export function getRCPos(url = window.location.href) {
    const { r = '', c = '', pos = '' } = query(url);
    return filterKeys({ r, c, pos }, 'r', 'c', 'pos');
}

// 适配图片，给每个图片加需要的后缀，/w/640
function imgSizeFit(url = '', type = 640) {
    // 小尺寸默认为头像
    if (!url && type == 96) {
        return WPT.noHeadImage;
    }

    // static/public文件夹下文件暂时不支持裁剪，等待余姚优化
    if (url.includes('weipaitang.com/static/public/')) {
        return url;
    }

    // 支持多种尺寸的图片服务器
    const picHosts = [
        'weipaitang.com/img/',
        'weipaitang.com/mmopen/',
        'weipaitang.com/static/',
        'weipaitang.com/community/',
        'weipaitang.com/certify/',
        'weipaitang.com/live/',
        'cdn02.youjiang.xin/',
        'weipaitang.com/activity/',
    ];
    if (picHosts.some((it) => url.includes(it))) {
        if (/-W\d+H\d+\/w\/(\d+)$/.test(url)) {
            // -W800H800/w/320 这样子的
            url = url.replace(/\/w\/(\d+)$/, '');
        }
        return `${url}/w/${type}`;
    }

    // 无法处理
    return url;
}

/**
 * 获取背景图样式
 *
 * @exports WPT/Util/getImgStyle
 * @param {String} [url=''] url
 * @param {Number} type 大小
 * @returns {Object}
 */
function getImgStyle(url = '', type) {
    if (!url) {
        return {};
    }

    const realUrl = type === undefined ? url : imgSizeFit(url, type);
    return {
        backgroundImage: `url(${realUrl})`,
    };
}

/**
 * 为特殊字符添加转义\\
 * https://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
 *
 * @exports WPT/Util/escapeRegExp
 * @param {String} [string='']
 * @returns {String}
 */
function escapeRegExp(string = '') {
    return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
}

/**
 * 预加载图片
 *
 * @exports WPT/Util/preLoadImages
 * @param {String|Array} 图片路径
 */
export function preLoadImages(urls) {
    [].concat(urls).forEach((url) => {
        new Image().src = url;
    });
}

/**
 * 热度取数
 * 超过9999的变成万级别
 * hot 向下取数 保留len位小数
 */
export function handleHot(hot = 0, len = 1) {
    const lenNum = Math.pow(10, len); /* eslint-disable-line no-restricted-properties */
    if (hot > 9999) {
        return `${Math.floor((hot / 10000) * lenNum) / lenNum}w`;
    }
    return hot;
}

/**
 * 安卓微信获取的localID是weixin://
 * 使用此url获取到对应的bolb是null,无法进行图片识别工作，因此需要转换一下
 * @param {*} urlObj
 * @returns
 */
function getAndroidImageUrl(urlObj) {
    const { localId = '', serverId = '' } = urlObj;
    if (!localId.startsWith('weixin://')) {
        return localId;
    }

    const apiError = WPT.Util.handleApi('/cors/wx-image', { useGoApi: true });
    const mediaId = serverId.slice(0, serverId.match(/-W\d+H\d+/)?.index)?.slice(8);

    return `https:${apiError}?mediaId=${mediaId}`;
}

/**
 * 数值显示转化
 * 3位数完全显示
 * 4位数显示x.xk
 * 5位数显示x.xw
 * @param {number} num 需要格式化的数字
 * @param {object} config 目前仅支持4数字和5位数字的格式化
 * @return {string} 格式化后的数字
 */
export function formatNumberUnit(num = '', config = { 4: 'k', 5: 'w' }) {
    // eslint-disable-next-line
    num = num.toString();
    if (num.length <= 3) {
        return num;
    }
    if (num.length === 4) {
        return `${num[0]}.${num[1]}${config[4]}`;
    }
    if (num.length === 5) {
        return `${num[0]}.${num[1]}${config[5]}`;
    }
    return `${num.slice(0, -4)}.${num.slice(-4, -3)}${config[5]}`;
}

/**
 * 获取今天剩余的秒数（一般用于设置自然日缓存有效期）
 * @return {number}
 */
export function getTodaySurplusSeconds() {
    const perDay = 24 * 60 * 60 * 1000;
    const now = new Date();
    return Math.floor((perDay - ((now.getTime() - now.getTimezoneOffset() * 60 * 1000) % perDay)) / 1000);
}

export default {
    urlParamToJson,
    getQueryString,
    getLastR,
    addUrlParam,
    base64encode,
    handelImgHeight,
    query, // string -> obj decodeUriComponent
    params, // obj -> url search
    urlAddQuery: params, // url obj -> str encodeUriComponent
    number_format,
    formatDate,
    formatMobile,
    formatNumber,
    formatCountDown,
    handleApi, // 处理请求api
    request, // 处理请求返回get/post方法return一个Promise
    getType, // 获取数据类型
    reload, // 浏览器刷新
    copyToClipboard, // 复制到剪贴板
    diffTime,
    getRandomNum,
    timeFormat,
    mergeQuery,
    diff, // 对象比对
    urlDiff, // url比对
    urlParse, // 解析url
    uuid, // 随机id
    smoothScroll, // 光滑的滚动
    execWxJob, // 执行微信任务
    getStamp, // 转时间戳
    isIn, // 是否时间内
    isBefore, // 是否某个时间前
    isAfter, // 是否某个时间后
    formatCountDownTime, // 表格式化
    Countdown, // 倒计时
    getCountdown,
    filterKeys, // 删除对象的空key
    animateScroll, // 滚动动画
    animateScrollLeft, // 水平滚动动画
    getRCPos,
    getImgStyle,
    imgSizeFit,
    escapeRegExp,
    preLoadImages,
    handleHot,
    getAndroidImageUrl,
    formatNumberUnit,
    getTodaySurplusSeconds,
};
